/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects } from "../../model/Defects";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { AstTreeUtils } from "arkanalyzer";
import { Rule } from "../../model/Rule";
import { IssueReport } from '../../model/Defects';
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoConfusingNonNullAssertionCheck');

interface RuleResult {
  line: number;
  character: number;
  endCol: number;  // 新增结束列字段
  message: string;
}

export class NoConfusingNonNullAssertionCheck implements BaseChecker {
  public metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-confusing-non-null-assertion-check.md',
    description: 'Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b',
  };
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const fileMatcher: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [fileMatcher];
  }

  public check = (targetField: ArkFile) => {
    const filePath = targetField.getFilePath();
    let code = targetField.getCode()?.toString() ?? '';
    const sourceFile = AstTreeUtils.getASTNode(targetField.getName(), code);
    this.checkConfusingNonNullAssertion(sourceFile).forEach((item) => {
      this.addIssueReport(item.line, item.character, item.endCol,filePath);
    });
  }
  private checkConfusingNonNullAssertion(sourceFile: ts.SourceFile): RuleResult[] {
    const results: RuleResult[] = [];
    function visit(node: ts.Node) {
      if (ts.isBinaryExpression(node)) {
        const operator = node.operatorToken.getText();
        if (operator === '==' || operator === '===' || operator === '=') {
          const isAssign = operator === '=';
          const leftHandFinalToken = node.left.getLastToken();
          const tokenAfterLeft = getNextToken(leftHandFinalToken, sourceFile);
          if (
            leftHandFinalToken?.kind === ts.SyntaxKind.ExclamationToken &&
            tokenAfterLeft?.kind !== ts.SyntaxKind.CloseParenToken
          ) {
            const leftHandPrimaryExpression = ts.isNonNullExpression(node.left);

            if (leftHandPrimaryExpression) {
              const nodeStart = node.getStart();
              const nodeEnd = node.getEnd();  // 获取整个表达式的结束位置
              const { line, character } = sourceFile.getLineAndCharacterOfPosition(nodeStart);
              const { character: endChar } = sourceFile.getLineAndCharacterOfPosition(nodeEnd);
              results.push({
                line: line + 1,
                character: character + 1,
                endCol: endChar + 1,  // 添加结束列
                message: isAssign
                  ? 'Confusing combinations of non-null assertion and equal test like "a! = b", which looks very similar to not equal "a != b".'
                  : 'Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b".',
              });
            } else {
              const nodeStart = node.getStart();
              const nodeEnd = node.getEnd();  // 获取整个表达式的结束位置
              const { line, character } = sourceFile.getLineAndCharacterOfPosition(nodeStart);
              const { character: endChar } = sourceFile.getLineAndCharacterOfPosition(nodeEnd);
              results.push({
                line: line + 1,
                character: character + 1,
                endCol: endChar + 1,  // 添加结束列
                message: 'Wrap up left hand to avoid putting non-null assertion "!" and "=" together.',
              });
            }
          }
        }
      }
      ts.forEachChild(node, visit);
    }

    /**
     * 获取给定标记之后的下一个标记
     * @param token 当前标记
     * @param sourceFile 当前源文件
     * @returns 返回下一个标记，如果不存在则返回undefined
     */
    function getNextToken(token: ts.Node | undefined, sourceFile: ts.SourceFile): ts.Node | undefined {
      if (!token) return undefined;
      const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, sourceFile.text);
      scanner.setTextPos(token.end);
      while (true) {
        const tokenSyntaxKind = scanner.scan();
        if (tokenSyntaxKind === ts.SyntaxKind.EndOfFileToken) break;
        const nextTokenStart = scanner.getTokenPos();
        const nextTokenEnd = scanner.getTextPos();
        // 获取下一个标记的文本内容
        const nextTokenText = sourceFile.text.substring(nextTokenStart, nextTokenEnd);
        // 检查是否为非空白和非注释的标记
        if (!/\s/.test(nextTokenText) && !isComment(nextTokenText)) {
          return findNodeAtPosition(sourceFile, nextTokenStart, nextTokenEnd);
        }
      }
      return undefined;
    }

    function findNodeAtPosition(sourceFile: ts.SourceFile, start: number, end: number): ts.Node | undefined {
      let result: ts.Node | undefined = undefined;
      ts.forEachChild(sourceFile, node => {
        if (node.pos <= start && node.end >= end) {
          result = node;
        }
      });
      return result;
    }
    /**
     * 检查给定的文本是否为注释
     * @param text 文本内容
     * @returns 是否为注释
     */
    function isComment(text: string): boolean {
      return /\/\*[\s\S]*?\*\/|\/\/.*/.test(text);
    }
    visit(sourceFile);
    return results;
  }

  private async addIssueReport(line: number, startCol: number,endCol:number, filePath: string) {
    const severity = this.rule.alert ?? this.metaData.severity;
    const description = `Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b`;
    const defect = new Defects(line, startCol, endCol,description, severity,
      this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defect, undefined))
    RuleListUtil.push(defect);
  }
}
